﻿using System;
using System.Diagnostics;
using System.Windows;
using DeepEarth.Map.Core;
using DeepEarth.Map.Core.Utilities;
using DeepEarth.Core;

namespace DeepEarth.Toolkit.Controls.Automation
{
    // source http://blogs.msdn.com/lutzg/
    public class DefaultDisplacement
    {
        // Rho controls the amount of zooming used.  sqrt(2) is an empirically determined value suggested in the paper.
        // A good value of rho depends on the metric used to control zoom.
        static readonly double Rho = Math.Sqrt(1.9);

        // average speed of motion in units of (size of viewport)/second.
        // We have a user specified transition time.
        // readonly double velocity = 1.1;

        // c0 and c1 are the positions of the center of the viewport at the beginning and end of the animation, respectively.
        // direction = c1 - c0.
        Point c0, c1, direction;
        // w is the width of the viewport.
        // u is the distance the center of the viewport has traveled.
        // b and r are constants used in the paper's equations.
        // S is the perceptual distance traveled in units of (size of viewport), including the effects of zooming.
        double w0, w1, u0, u1, b0, b1, r0, r1, S;

        double pitchDelta, headingDelta, scaleDelta;

        public DefaultDisplacement(MapViewLite initialView, MapViewLite targetView)
        {
            InitialView = initialView;
            TargetView = targetView;

            c0 = CoordinateTransformation.GeographicToLogical(InitialView.Center);
            c1 = CoordinateTransformation.GeographicToLogical(TargetView.Center);

            if (c0 != c1)
            {
                direction = new Point(c1.X - c0.X, c1.Y - c0.Y);

                direction.X = LimitTo180Degrees(direction.X);

                w0 = initialView.ViewportWidth;
                w1 = targetView.ViewportWidth;

                u0 = 0;
                u1 = Math.Sqrt(direction.X * direction.X + direction.Y * direction.Y);

                // See page 5 of the paper for these equations.
                b0 = (w1 * w1 - w0 * w0 + Rho * Rho * Rho * Rho * u1 * u1) / (2 * w0 * Rho * Rho * u1);
                b1 = (w1 * w1 - w0 * w0 - Rho * Rho * Rho * Rho * u1 * u1) / (2 * w1 * Rho * Rho * u1);

                r0 = Math.Log(-b0 + Math.Sqrt(b0 * b0 + 1));
                r1 = Math.Log(-b1 + Math.Sqrt(b1 * b1 + 1));

                S = (r1 - r0) / Rho;
            }
        }

        #region Properties
        public MapViewLite InitialView { get; private set; }
        public MapViewLite TargetView { get; private set; }
        #endregion

        public MapViewLite CalculatePosition(double newValue)
        {
            if (TargetView == InitialView)
                return TargetView;
            // See page 5 of the paper for these equations.
            // s goes from 0 to S as Progress goes from 0 to 1
            var s = newValue * S;
            // Now compute u(s) and w(s)
            var u = w0 / (Rho * Rho) * Math.Cosh(r0) * Math.Tanh(Rho * s + r0) - w0 / (Rho * Rho) * Math.Sinh(r0) + u0;
            var w = w0 * Math.Cosh(r0) / Math.Cosh(Rho * s + r0);

            var targetCentreX = c0.X + direction.X * u / u1;
            var targetCenterY = c0.Y + direction.Y * u / u1;

            var targetLocation = CoordinateTransformation.LogicalToGeographic(new Point(targetCentreX, targetCenterY));

            var targetLongitude = targetLocation.Longitude;
            var targetLatitude = targetLocation.Latitude;

            var targetViewportWidth = w;

            // handle map zoom only transitions
            if (InitialView.Center.Latitude == TargetView.Center.Latitude && InitialView.Center.Longitude == TargetView.Center.Longitude)
            {
                targetLatitude = TargetView.Center.Latitude;
                targetLongitude = TargetView.Center.Longitude;
            }

            return new MapViewLite()
            {
                Center = new Location { Latitude = targetLatitude, Longitude = targetLongitude },
                ViewportWidth = targetViewportWidth
            };
        }

        /// <summary>
        /// Adjusts an angle so it is between +-180 degrees
        /// </summary>
        double LimitTo180Degrees(double angle)
        {
            if (angle > 180) return angle - 360;
            if (angle < -180) return angle + 360;
            return angle;
        }
    }
}